
#if !defined( xmlscanner_h )
#define xmlscanner_h

#include <stdlib.h>
#include <string.h>

//
//  NOTHING IN XMLSCANNER CAN BE TRUSTED TO BE ZERO TERMINATED!!
//
//  You must check the valueSize() to get a value, and keySize() to get a key.
//  It's done this way to provide the quickest possible, no-allocation scanner. 
//  The function strnicmp() is your friend, as is sprintf("%.*s").
//

//  XMLScanner does not mutate the argument buffer in-place.
//  XMLScanner does not convert XML entities to text.
//  XMLScanner does not use strict XML character conventions, 
//  XMLScanner does not allocate memory -- it hands you pointer/size pairs to your data.
//  XMLScanner recognizes specifically <, >, / (at start of tag), 
//  whitespace and non-whitespace as separators.
//  For CDATA, leading and trailing whitespace all get 
//  shoved into separate whitespace entities; whitespace 
//  embedded into the CDATA is part of the CDATA.
//  <tag foo="1"> this is some text </tag>
//  Tag("tag"), Attribute("foo","1"), Whitespace(" "), CDATA("this is some text"), Whitespace(" "), Close("tag")

class XMLScanner {
  public:

    struct Token {
      enum Kind {
        Error,
        Whitespace,
        CDATA,
        Tag,
        Attribute,
        Close,
        EndOfFile
      };
      Kind kind;
      char const * key;     //  NOT zero terminated!
                            //  tag, attribute, close, error (indicator)
      size_t keySize;
      char const * value;   //  NOT zero terminated!
                            //  attribute, whitespace, cdata, error (error msg) (also points at close tag on Close)
      size_t valueSize;

      inline bool keyIs( char const * str ) const {
        size_t l = strlen( str );
        return key && keySize == l && !strncmp( str, key, l );
      }
      inline bool valueIs( char const * str ) const {
        size_t l = strlen( str );
        return value && valueSize == l && !strncmp( str, value, l );
      }
    };

    //  TODO: perhaps there should be a way to put the scanner in lenient mode?
    XMLScanner( char const * buf, size_t size );
    ~XMLScanner();
    
    // Tokens returned by nextToken() point into the data 
    // stream that you supplied. nextToken() returns false 
    // on error or end of file.
    bool nextToken( XMLScanner::Token * outToken );

    // tokenAtDepth(0) is the last returned token, and may 
    // be of any kind. tokenAtDepth(1..N) are always Tag 
    // tokens. Depth must be between [0,depth()].
    size_t depth() const;
    XMLScanner::Token const * tokenAtDepth( size_t depth );

  private:
    enum {
      StackDepth = 32
    };
    size_t stackPtr_;
    Token stack_[StackDepth];
    char const * buf_;
    char const * end_;
};



//  XMLNode is used by XMLDom to reference some piece of 
//  the XML document (tag, attribute, whitespace or cdata).
//TODO:  XMLNode is entirely manated by XMLDom -- perhaps it 
//  should be a member of XMLDom...
class XMLNode {
  public:

    XMLScanner::Token const & token() const { return token_; }
    //  operator[] returns all kinds of child nodes
    XMLNode * operator[]( size_t ix ) const;
    //  child() returns only attributes and child tags.
    //  Note that these may have whitespace/cdata as siblings.
    XMLNode * child( size_t ix = 0 );
    //  text() returns only whitespace and cdata.
    //  Note that these may have tags as siblings (but not attributes).
    XMLNode * text( size_t ix );
    //  parent() returns the parent, or NULL for the root.
    XMLNode * parent() const { return parent_; }
    //  next() returns the next sibling.
    XMLNode * next() const { return siblings_; }

  private:
    XMLNode();
    friend class XMLDom;
    XMLScanner::Token token_;
    XMLNode * children_;
    XMLNode * siblings_;
    XMLNode * parent_;
    //  Sheesh, this class is big! Perhaps remove the caching for iteration?
    struct IterState {
      size_t ix;
      XMLNode * node;
    };
    mutable IterState arrayIter_;
    mutable IterState childIter_;
    mutable IterState textIter_;
};


//  XMLDom will, given a buffer and size, parse an XML 
//  document, and 
class XMLDom {
  public:
    XMLDom( char const * buffer, size_t size, void (*freeFunc)( char const * ) = 0 );
    ~XMLDom();

    XMLNode * root();
    char const * error();

  private:

    char const * buffer_;
    size_t size_;
    void (*freeFunc_)( char const * );

    XMLNode * root_;
    XMLNode * error_;
    void parse( XMLNode * parent, XMLNode ** root, XMLScanner & s );

    //  node memory management
    //  TODO: should use malloc and placement new
    XMLNode ** nodes_;
    size_t numBlocks_;
    XMLNode * nextNode_;
    size_t nodesLeft_;
    XMLNode * newNode();
};


#endif  //  xmlscanner_h

